package cn.felord.app.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.security.auth.UserPrincipal;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.config.AbstractMessageBrokerConfiguration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

import java.security.Principal;
import java.util.Objects;

/**
 * Stomp web socket configuration.
 * <p>
 * {@link org.springframework.messaging.handler.annotation.MessageMapping}   客户端 订阅地址 配合 sub
 *
 * @author dax
 * @see AbstractMessageBrokerConfiguration#brokerMessageConverter()
 * @since 2019 /4/13 18:45
 */
@Configuration
@ConditionalOnClass({ObjectMapper.class})
@EnableWebSocketMessageBroker
public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer {


    /**
     * 添加一个stomp服务端点，来接收客户端的连接。
     * 将 “/chat” 路径注册为 STOMP 端点。
     * 这个路径与之前发送和接收消息的目的路径有所不同， 这是一个端点，客户端在订阅或发布消息到目的地址前，要连接该端点，
     * 即用户发送请求 ：url=’/127.0.0.1:8080/chat’ 与 STOMP server 进行连接，之后再转发到订阅url；
     *
     * @param registry reg
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/chat")
                .setAllowedOrigins("*")
                .withSockJS();
    }


    /**
     * inbound通道配置  可以对 入站消息进行统一的处理
     * {@link this#configureClientOutboundChannel(ChannelRegistration)} 相反
     *
     * @param registration channel
     */
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        // 拦截器获取 header 的一些参数进行校验等操作
        ChannelInterceptor headerChannelInterceptor = new ChannelInterceptor() {
            @Override
            public Message<?> preSend(Message<?> message, MessageChannel channel) {
                StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);

                if (StompCommand.CONNECT.equals(Objects.requireNonNull(accessor).getCommand())) {
                    String username = accessor.getFirstNativeHeader("username");
                    if (StringUtils.hasLength(username)) {
                        // 绑定user
                        Principal principal = new UserPrincipal(username);
                        accessor.setUser(principal);
                    }

                }
                return message;
            }
        };

        registration.interceptors(headerChannelInterceptor);
    }

    /**
     * 配置了一个 简单的消息代理，通俗一点讲就是设置消息连接请求的各种规范信息
     * <p>
     * {@link MessageBrokerRegistry#enableSimpleBroker(String...)} 订阅broker 名称
     * <p>
     * {@link MessageBrokerRegistry#setApplicationDestinationPrefixes(String...)} 客户端请求前缀 例如客户端发送消息的目的地为/app/sendTest，
     * 则对应控制层@MessageMapping(“/sendTest”), 客户端订阅主题的目的地为/app/subscribeTest，则对应控制层@SubscribeMapping(“/subscribeTest”)
     * <p>
     * {@link MessageBrokerRegistry#setUserDestinationPrefix(String)}  点对点使用的订阅前缀（客户端订阅路径上会体现出来），不设置的话，默认是/user
     *
     * @param registry MessageBrokerRegistry
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        //定义了两个客户端订阅地址的前缀信息，也就是客户端接收服务端发送消息的前缀信息   比如浏览器 sub
        registry.enableSimpleBroker("/message", "/notice");
        //定义了服务端接收地址的前缀，也即客户端给服务端发消息的地址前缀    比如浏览器send
        registry.setApplicationDestinationPrefixes("/app");

    }
}
